package cn.itsource.ymcc.service.impl;

import cn.itsource.util.AssertUtil;
import cn.itsource.util.CodeGenerateUtils;
import cn.itsource.ymcc.domain.Course;
import cn.itsource.ymcc.domain.CourseMarket;
import cn.itsource.ymcc.domain.KillActivity;
import cn.itsource.ymcc.domain.KillCourse;
import cn.itsource.ymcc.dto.KillParamDto;
import cn.itsource.ymcc.dto.PreCourseOrder;
import cn.itsource.ymcc.mapper.KillCourseMapper;
import cn.itsource.ymcc.service.IKillActivityService;
import cn.itsource.ymcc.service.IKillCourseService;
import cn.itsource.ymcc.vo.Course2OrderInfoVo;
import cn.itsource.ymcc.vo.Course2OrderVo;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Cherish
 * @since 2022-08-23
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private IKillActivityService killActivityService;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    @Transactional
    public void add(KillCourse killCourse) {
        // <------------------参数校验---------------------->
        //校验课程状态,若还没有上架则不能加入活动，同一活动中不能加入重复的课程
        Long activityId = killCourse.getActivityId();
        KillCourse killCourseTmp = selectByActivityIdAndCourseId(activityId,killCourse.getCourseId());
        AssertUtil.isNull(killCourseTmp ,"不能添加重复课程");
        // 校验 是否存在该活动，并且活动状态必须是待发布，才能加入秒杀
        KillActivity killActivity = killActivityService.selectById(activityId);
        AssertUtil.isNotNull(killActivity,"该活动不存");
        boolean isWait = killActivity.getPublishStatus() == KillActivity.STATES_WAIT_PUBLISH;
        AssertUtil.isTrue(isWait,"该活动已发布，状态不满足！");
        // 保存数据
        killCourse.setPublishStatus(killActivity.getPublishStatus());
        killCourse.setStartTime(killActivity.getBeginTime());
        killCourse.setEndTime(killActivity.getEndTime());
        killCourse.setTimeStr(killActivity.getTimeStr());
        killCourse.setKillLimit(1);
        killCourse.setCreateTime(new Date());
        insert(killCourse);
    }

    @Override
    public List<KillCourse> onlineAll() {
        // 1.匹配所有前缀为activity得key
        Set<Object> keys = redisTemplate.keys("activity:*");
        // 2.遍历拿到所有key对应得value值
        List<KillCourse> killCourses = new ArrayList<>();
        keys.forEach(key->{
            List values = redisTemplate.opsForHash().values(key);
            killCourses.addAll(values);
        });
        // 3.封装集合返回数据
        return killCourses;
    }

    @Override
    public KillCourse onlineOne(Long activityId, Long killId) {
        KillCourse killCourse = (KillCourse) redisTemplate.opsForHash().get(
                "activity:" + activityId,
                killId.toString()
        );
        return killCourse;
    }


    // 1.点击秒杀按钮 传递相关参数过去
    // 2.后端接受参数执行秒杀
    //    各种业务判断
    //    不能重复秒杀
    //    同一个人，在本次活动中之后秒杀同一个商品一次
    //    秒杀课程时间校验
    // 3.找到你要秒杀的秒杀课程，尝试扣减库存信号量
    // 4.扣减不成功，手速太慢，请重新秒杀
    // 5.扣减成功，生成预创订单
    // 6.讲预创订单保存到redis
    // 7.返回订单号
    @Override
    @Transactional
    public String kill(KillParamDto dto) {
        Date startTime = new Date();
        KillCourse killCourse = onlineOne(dto.getActivityId(), dto.getKillCourseId());
        AssertUtil.isTrue(killCourse.isKilling(),"不存在！！！");

        String semaphoreKey = dto.getActivityId() + ":" + dto.getKillCourseId();
        RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
        boolean tryAcquire = semaphore.tryAcquire(1);//扣减信号量（信号量不可能为负数，所以不存在超卖问题）

        AssertUtil.isTrue(tryAcquire,"手速太慢，下次再来！");

        Long loginId = 3L;
        PreCourseOrder preOrder = new PreCourseOrder();
        preOrder.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        preOrder.setTotalAmount(killCourse.getKillPrice());
        preOrder.setTotalCount(1);
        preOrder.setUserId(loginId);
        preOrder.setCourseId(killCourse.getCourseId());
        preOrder.setCourseName(killCourse.getCourseName());
        preOrder.setCoursePic(killCourse.getCoursePic());

        redisTemplate.opsForValue().set(preOrder.getOrderNo(),preOrder);
        long timemi = System.currentTimeMillis() - startTime.getTime();
        log.info("秒杀执行时间：{}",timemi);

        return preOrder.getOrderNo();
    }

    @Override
    public Course2OrderInfoVo queryPreOrder(String orderNo) {
        // 参数校验
        AssertUtil.isNotEmpty(orderNo,"非法请求");
        Object objTmp =  redisTemplate.opsForValue().get(orderNo);
        AssertUtil.isNotNull(objTmp,"预创单不存在！");

        // 封装数据并且返回
        PreCourseOrder preCourseOrder = (PreCourseOrder) objTmp;
        Course2OrderInfoVo courseVo = new Course2OrderInfoVo();
        List<Course2OrderVo> courseInfos = new ArrayList<>();

        Course course = new Course();
        course.setId(preCourseOrder.getCourseId());
        course.setName(preCourseOrder.getCourseName());
        course.setPic(preCourseOrder.getCoursePic());

        CourseMarket courseMarket = new CourseMarket();
        courseMarket.setPrice(preCourseOrder.getTotalAmount());
        courseInfos.add(new Course2OrderVo(course,courseMarket));
        courseVo.setTotalAmount(courseMarket.getPrice());
        courseVo.setCourseInfos(courseInfos);

        return courseVo;
    }

    private KillCourse selectByActivityIdAndCourseId(Long activityId, Long courseId) {
        Wrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("course_id",courseId);
        wrapper.eq("activity_id",activityId);
        return selectOne(wrapper);
    }
}
